#pragma once

#include <cstring>
#include <sstream>
#include <string>
#include <utility>
#include "Bytes.h"
#include "Logger.h"
#include "Traits.h"
#include "Types.h"
#include <msgpack.hpp>

namespace zeroplus::mq
{
    namespace Serializer
    { // NOLINT

        template <uint8_t OffsetT = 0, typename SrcT>
        bool serialize(const SrcT &src, Bytes &des);

        template <typename DesT>
        bool deserialize(const Bytes &src, DesT &des);

        template <typename SrcT, typename DesT>
        bool convert(const SrcT &src, DesT &des);


        template <uint8_t OffsetT = 0, typename SrcT>
        inline bool serialize(const SrcT &src, Bytes &des)
        {
            if constexpr (std::is_same_v<SrcT, Bytes>)
            {
                des = Bytes::deep_copy(src.data(), src.size(), OffsetT);
                return true;
            }
            else if constexpr (MQ_HAS_MEMBER(SrcT, is_mq_dynamic_data()))
            {
                if (src.is_empty() || src.get_data().offset() == 0 || !src.get_data().is_own())
                {
                    LOG_ERROR("Dynamic data is empty.");
                    return false;
                }
                des = Bytes::shallow_copy(src.get_data().data(), src.get_data().size() + src.get_data().offset());
            }
            else if constexpr (kMqHasProtobuf && MQ_HAS_MEMBER(SrcT, SerializeToArray(0, 0)))
            {
                if constexpr (MQ_HAS_MEMBER(SrcT, ByteSizeLong()))
                {
                    des = Bytes::create(src.ByteSizeLong(), OffsetT);
                }
                else
                {
                    des = Bytes::create(src.ByteSize(), OffsetT);
                }
                if (!des.empty())
                {
                    if (!src.SerializeToArray(des.data(), des.size()))
                    {
                        LOG_ERROR("Protobuf serialize failed.");
                        return false;
                    }
                }
            }
            else if constexpr (Traits::Operatorable<std::decay_t<SrcT>, Bytes>())
            {
                const_cast<SrcT &>(src) >> des;
                if constexpr (OffsetT > 0)
                {
                    des = Bytes::deep_copy(des.data(), des.size(), OffsetT);
                }
            }
            else if constexpr (kMqHasMsgpack)
            {
                msgpack::sbuffer sbuf;
                msgpack::pack(sbuf, src);
                des = Bytes::deep_copy(reinterpret_cast<const uint8_t *>(sbuf.data()), sbuf.size(), OffsetT);
            }
            else if constexpr (std::is_same_v<SrcT, std::string>)
            {
                des = Bytes::deep_copy(reinterpret_cast<const uint8_t *>(src.data()), src.size(), OffsetT);
            }
            else if constexpr (std::is_constructible_v<std::string, SrcT>)
            {
                des = Bytes::deep_copy(reinterpret_cast<const uint8_t *>(src), std::strlen(src), OffsetT);
            }
            else if constexpr (!std::is_pointer_v<SrcT> && Traits::Operatorable<std::stringstream, std::decay_t<SrcT>>())
            {
                std::stringstream ss;
                ss << src;
                const std::string &str = ss.str();
                des = Bytes::deep_copy(reinterpret_cast<const uint8_t *>(str.data()), str.size(), OffsetT);
            }
            else if constexpr (!std::is_pointer_v<SrcT> && std::is_trivial_v<SrcT> && std::is_standard_layout_v<SrcT>)
            {
                const auto *src_ptr = reinterpret_cast<const uint8_t *>(&src);
                des = Bytes::deep_copy(src_ptr, sizeof(SrcT), OffsetT);
            }
            else
            {
                static_assert(Traits::ExpectFalse<SrcT>(), "Serialize not support.");
                return false;
            }
            return true;
        }

        template <typename DesT>
        inline bool deserialize(const Bytes &src, DesT &des)
        {
            if constexpr (std::is_same_v<DesT, Bytes>)
            {
                des = src;
                return true;
            }
            else if constexpr (MQ_HAS_MEMBER(DesT, is_mq_dynamic_data()))
            {
                if (src.empty() || src.size() < des.get_offset())
                {
                    LOG_ERROR("Dynamic data is empty.");
                    return false;
                }
                des.load(src);
            }
            else if constexpr (kMqHasProtobuf && MQ_HAS_MEMBER(DesT, ParseFromArray(0, 0)))
            {
                if (!src.empty())
                {
                    if (!des.ParseFromArray(src.data(), src.size()))
                    {
                        LOG_ERROR("Protobuf deserialize failed.");
                        return false;
                    }
                }
            }
            else if constexpr (Traits::Operatorable<std::decay_t<DesT>, Bytes>())
            {
                des << src;
            }
            else if constexpr (kMqHasMsgpack)
            {
                msgpack::object_handle oh = msgpack::unpack(reinterpret_cast<const char *>(src.data()), src.size());
                msgpack::object deserialized = oh.get();
                des = deserialized.as<DesT>();
            }
            else if constexpr (std::is_same_v<DesT, std::string>)
            {
                if (!src.empty())
                {
                    des = std::string(reinterpret_cast<const char *>(src.data()), src.size());
                }
                else
                {
                    des = std::string();
                }
            }
            else if constexpr (std::is_constructible_v<std::string, DesT>)
            {
                if (!src.empty())
                {
                    des = reinterpret_cast<const char *>(src.data());
                }
                else
                {
                    des = "";
                }
            }
            else if constexpr (!std::is_pointer_v<DesT> && Traits::Operatorable<std::stringstream, std::decay_t<DesT>>())
            {
                std::stringstream ss(std::string(reinterpret_cast<const char *>(src.data()), src.size()));
                ss >> des;
            }
            else if constexpr (!std::is_pointer_v<DesT> && std::is_trivial_v<DesT> && std::is_standard_layout_v<DesT>)
            {
                if (!src.empty())
                {
                    std::memcpy(&des, src.data(), src.size());
                }
                else
                {
                    LOG_ERROR("Standard layout deserialize failed.");
                    return false;
                }
            }
            else
            {
                static_assert(Traits::ExpectFalse<DesT>(), "Deserialize not support.");
                return false;
            }
            return true;
        }

        template <typename SrcT, typename DesT>
        inline bool convert(const SrcT &src, DesT &des)
        {
            static_assert(std::is_same_v<SrcT, Bytes> || std::is_same_v<DesT, Bytes>, "SrcT or DesT must be Bytes.");
            if constexpr (std::is_same_v<DesT, Bytes>)
            {
                return serialize(src, des);
            }
            else if constexpr (std::is_same_v<SrcT, Bytes>)
            {
                return deserialize(src, des);
            }
            else
            {
                static_assert(Traits::ExpectFalse<SrcT>(), "Input error.");
                return false;
            }
        }

    } // namespace Serializer

} // namespace zeroplus::mq
